package conf

import (
	"context"
	"database/sql"
	"fmt"
	"sync"
	"time"

	_ "github.com/go-sql-driver/mysql"
	orm_mysql "gorm.io/driver/mysql"
	"gorm.io/gorm"
)

const (
	CIPHER_TEXT_PREFIX = "@ciphered@"
)

func newConfig() *Config {
	return &Config{
		App:   newDefaultAPP(),
		MySQL: newDefaultMySQL(),
	}
}

type Config struct {
	App   *app   `toml:"app"`
	MySQL *mysql `toml:"mysql"`
}

type app struct {
	Name string `toml:"name" env:"APP_NAME"`
	HTTP *http  `toml:"http"`
	GRPC *grpc  `toml:"grpc"`
}

func newDefaultAPP() *app {
	return &app{
		Name: "mcenter",
		HTTP: newDefaultHTTP(),
		GRPC: newDefaultGRPC(),
	}
}

type http struct {
	Host string `toml:"host" env:"HTTP_HOST"`
	Port string `toml:"port" env:"HTTP_PORT"`
}

func (a *http) Addr() string {
	return a.Host + ":" + a.Port
}

func newDefaultHTTP() *http {
	return &http{
		Host: "127.0.0.1",
		Port: "8020",
	}
}

type grpc struct {
	Host string `toml:"host" env:"GRPC_HOST"`
	Port string `toml:"port" env:"GRPC_PORT"`
}

func (a *grpc) Addr() string {
	return a.Host + ":" + a.Port
}

func newDefaultGRPC() *grpc {
	return &grpc{
		Host: "127.0.0.1",
		Port: "18020",
	}
}

func newDefaultMySQL() *mysql {
	return &mysql{
		Host:        "127.0.0.1",
		DB:          "cmdb",
		Port:        3306,
		Username:    "root",
		Password:    "123456",
		MaxOpenConn: 50,
		MaxIdleConn: 10,
	}
}

// 连接MySQL数据库的配置
type mysql struct {
	Host     string `toml:"host" env:"MYSQL_HOST"`
	Port     int    `toml:"port" env:"MYSQL_PORT"`
	DB       string `toml:"db" env:"MYSQL_DB"`
	Username string `toml:"username" env:"MYSQL_USERNAME"`
	Password string `toml:"password" env:"MYSQL_PASSWORD"`

	// 高级参数
	MaxOpenConn int `toml:"max_open_conn" env:"MYSQL_MAX_OPEN_CONN"`
	MaxIdleConn int `toml:"max_idle_conn" env:"MYSQL_MAX_IDLE_CONN"`
	MaxLifeTime int `toml:"max_life_time" env:"MYSQL_MAX_LIFE_TIME"`
	MaxIdleTime int `toml:"max_idle_time" env:"MYSQL_MAX_IDLE_TIME"`

	// 面临并发安全
	lock sync.Mutex
	db   *gorm.DB
}

// 获取连接池对象,
// 驱动: _ "github.com/go-sql-driver/mysql"
// 适配标准库: sql.DB, 连接池(在驱动层之上)
func (m *mysql) GetConnPool() (*sql.DB, error) {
	var err error
	// multiStatements 让db 可以执行多个语句 select; insert;
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&multiStatements=true",
		m.Username, m.Password, m.Host, m.Port, m.DB)
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, fmt.Errorf("connect to mysql<%s> error, %s", dsn, err.Error())
	}

	// 对连接池进行设置
	db.SetMaxOpenConns(m.MaxOpenConn)
	db.SetMaxIdleConns(m.MaxIdleConn)
	if m.MaxLifeTime != 0 {
		db.SetConnMaxLifetime(time.Second * time.Duration(m.MaxLifeTime))
	}
	if m.MaxIdleConn != 0 {
		db.SetConnMaxIdleTime(time.Second * time.Duration(m.MaxIdleTime))
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		return nil, fmt.Errorf("ping mysql<%s> error, %s", dsn, err.Error())
	}
	return db, nil
}

// 这里的*gorm.DB 是一个单列实例
// ORM DB 已经封装了sql.DB(标准库)， 这个对象有很多高阶配置，比如 最大连接说, 活跃连接数
// 直接操作Obj -ORM-> DB
func (m *mysql) ORM() *gorm.DB {
	m.lock.Lock()
	defer m.lock.Unlock()

	if m.db == nil {
		// 初始化DB
		// 1.1 获取sql.DB
		p, err := m.GetConnPool()
		if err != nil {
			panic(err)
		}

		// 1.2 使用pool 初始化orm db对象
		m.db, err = gorm.Open(orm_mysql.New(orm_mysql.Config{
			Conn: p,
		}), &gorm.Config{
			// 执行任何 SQL 时都创建并缓存预编译语句，可以提高后续的调用速度
			PrepareStmt: true,
			// 对于写操作（创建、更新、删除），为了确保数据的完整性，GORM 会将它们封装在事务内运行。
			// 但这会降低性能，如果没有这方面的要求，您可以在初始化时禁用它，这将获得大约 30%+ 性能提升
			SkipDefaultTransaction: true,
			// 要有效地插入大量记录，请将一个 slice 传递给 Create 方法
			// CreateBatchSize: 200,
		})
		if err != nil {
			panic(err)
		}
	}
	return m.db
}
